﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;

namespace Common.Utilities.TextMatch
{
    /// <summary>
    /// 
    /// </summary>
    public sealed class MetaphoneConverter : PhoneticConverter
    {

        /// <summary>
        /// 
        /// </summary>
        public MetaphoneConverter() { }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="resultLength"></param>
        public MetaphoneConverter(int resultLength) : base(resultLength) { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public override string Convert(string value)
        {
            value = StringUtil.RemoveNonAlphaNumericCharacters(value);
            //Ensure the value parameter is valid
            Debug.Assert(value != null, "Unexpected null value.", "The MetaphoneConverter.Convert(String) method requires a value.");

            if (value == null)
                throw new ArgumentNullException("value");

            //Validate input
            if (!(new AlphabeticValidator().Validate(value)))
            {
                return string.Empty;
                // throw new ArgumentOutOfRangeException("value", value, NamespaceUtility.Resource["Error.InvalidPhoneticInput"]);
            }
            else
                return (DoMetaphone(value.ToUpper(CultureInfo.InvariantCulture), this.ResultLength));
        }


        //Do the actual Metaphone
        private string DoMetaphone(string value, int resultLength)
        {
            StringBuilder encodingBuilder = new StringBuilder();
            int charIndex = 0;

            //Do the BOW checks
            if (!string.IsNullOrEmpty(value) && value.Length >= 2)
                BeginningOfWordChecks(value, encodingBuilder, ref charIndex);

            //Setup the previous char
            char previousChar = Char.MinValue;
            if (charIndex > 0)
                previousChar = value[charIndex - 1];

            while (charIndex < value.Length)
            {
                char currentChar = value[charIndex];
                char nextChar = (charIndex + 1 < value.Length ? value[charIndex + 1] : Char.MinValue);
                char afterNextChar = (charIndex + 2 < value.Length ? value[charIndex + 2] : Char.MinValue);

                //Silence the vowels
                if (IsVowel(currentChar) || (previousChar == currentChar && currentChar != 'C'))
                    charIndex++;
                else if (currentChar == 'B')
                {
                    if (charIndex < value.Length)
                        encodingBuilder.Append("B");
                    charIndex++;
                }
                else if (currentChar == 'C')
                {
                    if (nextChar == 'H')
                    {
                        encodingBuilder.Append("X");
                        charIndex++;
                    }
                    else if (nextChar == 'I' && afterNextChar == 'A')
                    {
                        encodingBuilder.Append("X");
                        charIndex = charIndex + 2;
                    }
                    else if (nextChar == 'I' || nextChar == 'E' || nextChar == 'Y')
                    {
                        encodingBuilder.Append("S");
                        charIndex++;
                    }
                    else
                    {
                        encodingBuilder.Append("K");
                        if (nextChar == 'H' && previousChar == 'S')
                            charIndex++;
                    }
                    charIndex++;
                }
                else if (currentChar == 'D')
                {
                    if (nextChar == 'G' && (afterNextChar == 'E' || afterNextChar == 'Y' || afterNextChar == 'I'))
                    {
                        encodingBuilder.Append("J");
                        charIndex = charIndex + 2;
                    }
                    else
                    {
                        encodingBuilder.Append("T");
                    }
                    charIndex++;
                }
                //PH to F
                else if (currentChar == 'P')
                {
                    if (nextChar == 'H')
                    {
                        encodingBuilder.Append("F");
                        charIndex++;
                    }
                    else
                    {
                        encodingBuilder.Append("P");
                    }
                    charIndex++;
                }
                //Z to S
                else if (currentChar == 'Z')
                {
                    encodingBuilder.Append("S");
                    charIndex++;
                }
                //Silent if not followed be vowel
                else if (currentChar == 'Y' || currentChar == 'W')
                {
                    if (IsVowel(nextChar))
                    {
                        encodingBuilder.Append(currentChar);
                        charIndex++;
                    }
                    charIndex++;
                }
                //X to KS
                else if (currentChar == 'X')
                {
                    encodingBuilder.Append("KS");
                    charIndex++;
                }
                //V to F
                else if (currentChar == 'V')
                {
                    encodingBuilder.Append("F");
                    charIndex++;
                }
                //Q to K
                else if (currentChar == 'Q')
                {
                    encodingBuilder.Append("K");
                    charIndex++;
                }
                //K silent after C
                else if (currentChar == 'K')
                {
                    if (previousChar != 'C')
                        encodingBuilder.Append("K");
                    charIndex++;
                }
                //H silent if after vowel with no following vowel
                else if (currentChar == 'H')
                {
                    if (!IsVowel(previousChar) && !IsVowel(nextChar))
                        encodingBuilder.Append("H");
                    charIndex++;
                }
                //S to X or S
                else if (currentChar == 'S')
                {
                    if (nextChar == 'H')
                    {
                        encodingBuilder.Append("X");
                        charIndex++;
                    }
                    else if (nextChar == 'I' && (afterNextChar == 'A' || afterNextChar == 'O'))
                    {
                        encodingBuilder.Append("X");
                        charIndex = charIndex + 2;
                    }
                    else
                        encodingBuilder.Append("S");
                    charIndex++;
                }
                //T to X or 0 or T or silent
                else if (currentChar == 'T')
                {
                    if (nextChar == 'H')
                    {
                        encodingBuilder.Append("0");
                        charIndex++;
                    }
                    else if (nextChar == 'I' && (afterNextChar == 'A' || afterNextChar == 'O'))
                    {
                        encodingBuilder.Append("X");
                        charIndex = charIndex + 2;
                    }
                    else if (nextChar == 'C' && afterNextChar == 'H')
                    {
                        //DO nothing
                    }
                    else
                        encodingBuilder.Append("T");
                    charIndex++;
                }
                //G
                else if (currentChar == 'G')
                {
                    if (nextChar == 'H' && !(IsVowel(afterNextChar) || charIndex == value.Length))
                        charIndex++;
                    else if (nextChar == 'N')
                    {
                        encodingBuilder.Append("N");
                        charIndex++;
                        if (value.Length >= (charIndex + 1 + 2) && value.Substring(charIndex + 1, 2) == "ED")
                            charIndex = charIndex + 2;
                    }
                    else if (nextChar == 'I' || nextChar == 'E' || nextChar == 'Y')
                    {
                        encodingBuilder.Append("J");
                        charIndex++;
                    }
                    else
                        encodingBuilder.Append("K");
                    charIndex++;
                }
                //Self to Self
                else
                {
                    encodingBuilder.Append(currentChar);
                    charIndex++;
                }

                //Exit if we've met our output limit
                if (encodingBuilder.Length == resultLength)
                    break;

                //Retain thh last char
                previousChar = currentChar;
            }

            return encodingBuilder.ToString();
        }

        //Is the char a vowel?
        private bool IsVowel(char c)
        {
            return ((c == 'A') || (c == 'E') || (c == 'I') || (c == 'O') || (c == 'U'));
        }

        //Check the beginning of the word for special cases...
        private void BeginningOfWordChecks(string value, StringBuilder encodingBuilder, ref int charIndex)
        {

            string beginningOfWord = value.Substring(0, 2);

            if (beginningOfWord == "AE" || beginningOfWord == "GN"
                || beginningOfWord == "KN" || beginningOfWord == "PN"
                || beginningOfWord == "WR")
            {
                //Remove the first character if the word starts: AE, GN, KN, PN or WR.
                encodingBuilder.Append(beginningOfWord.Substring(1, 1));
                charIndex = 2;
            }
            else if (beginningOfWord == "WH")
            {
                //WH at the beginning of a w  ord becomes W.
                encodingBuilder.Append("W");
                charIndex = 2;
            }
            else
            {
                if (beginningOfWord[0] == 'X')
                {
                    encodingBuilder.Append("S");
                    charIndex = 1;
                }
                else if (IsVowel(beginningOfWord[0]))
                {
                    encodingBuilder.Append(beginningOfWord[0]);
                    charIndex = 1;
                }
            }

        }

    }
}
